/*
 * CLASS CAMERA
 * ============
 * Manages the eye position and the position of the image plate or Canvas
 * where we will build images.  
 * The canvas position is the central point in the plate.
 * Camera class can generate light beams, so we can detect collitions with 
 * visual objects using the Ray Caster
 */
package rayCaster;

import static rayCaster.ApplicationInterface.*;
import java.util.ArrayList;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import static java.lang.Math.*;

public class Camera {
	private Point3d eyePosition;
	private Point3d canvasPosition;
	private Point3d verticalReferencePoint;
	private ImagePlate canvas;
	double visualAngle;
	
	/*
	 * By default this constructor takes default definitions in LibInterface
	 */
	public Camera(){		
		eyePosition = new Point3d(CAMERA_POSITION);
		canvasPosition = new Point3d(CANVAS_POSITION);
		canvas = new ImagePlate(WIDTH, HEIGHT);
		visualAngle = VISUAL_ANGLE;
		verticalReferencePoint = new Point3d(VERT_REFERENCE);
	}	
	
	public Camera( int canvasWidth, int canvasHeight ){
		this();
		canvas = new ImagePlate( canvasWidth, canvasHeight );
	}
	
	public Camera( int canvasWidth, int canvasHeight, double visualAngle ){
		this();
		setVisualAngle(visualAngle);
		canvas = new ImagePlate( canvasWidth, canvasHeight );
	}
	
	public Camera(Point3d eyePosition, Point3d platePosition, Point3d verticalReference, int canvasWidth, int canvasHeight){
		this();
		this.eyePosition = (Point3d)eyePosition.clone();
		this.canvasPosition = (Point3d)platePosition.clone();
		verticalReference = (Point3d)verticalReference.clone();
		canvas = new ImagePlate( canvasWidth, canvasHeight );
	}
		
	public int getCanvasWidth(){
		return canvas.getWidth();
	}
	
	public int getCanvasHeight(){
		return canvas.getHeight();
	}
	
	public void setEyePosition(Point3d anotherPosition){
		eyePosition = (Point3d)anotherPosition.clone();
	}
	
	public Point3d getEyePosition(){
		return (Point3d)eyePosition.clone();
	}
	
	public void setCanvasPosition(Point3d anotherPosition){
		canvasPosition = (Point3d)anotherPosition.clone();
	}
	
	public Point3d getCanvasPosition(){
		return (Point3d)canvasPosition.clone();
	}
	
	public void setVisualAngle( double otherAngle ){		
		this.visualAngle = otherAngle % 360;
	}
	public double getVisualAngle(){
		return visualAngle;
	}
	
	public void setVerticalReferencePoint( Point3d otherReference ){
		verticalReferencePoint = new Point3d(otherReference);
	}
	
	public Point3d getVerticalReferencePoint(){
		return new Point3d(verticalReferencePoint);
	}
	
	public ImagePlate getCanvas(){
		return canvas;
	}
	
	public double getCanvasDistance(){
		return canvasPosition.distance(eyePosition);
	}
	
	public void setCanvasDistance(double distance){
		//Get the versor in the direction of the new canvas position
		Vector3d ans = this.getCameraDirection();
		
		//Scale it with new distance. Now we have a vector that is the right
		//length. Just add the eyePosition, and we have new the canvasPosition
		ans.scale(distance);
		ans.add(eyePosition);
		canvasPosition.set(ans);		
	}
	
	/*
	 * We need to traverse the canvas in discrete steps. For doing this we
	 * need 2 things:
	 * 
	 * 1 - The stepSize
	 *     ============
	 * 	   	Formula used:
	 * 						 	       canvasWidth * stepSize
	 * 			tan(visualAngle/2)  =   ------------------------
	 * 					  		         2 * canvasDistance
	 * 
	 * 		where every variable is known except for the stepSize need to 
	 * 		traverse the whole canvas plate
	 * 
	 * 2 - Canvas Parallel Direction 
	 * 	   =========================
	 * 		To move among points that belong to the canvas plane. For this we
	 * 		used a simplified version at this time. We asume the camera will
	 * 		always has a direction parallel to the Z axis.
	 * 		In this case, the camera direction and the X-axis will be 
	 * 		perpendicular to each other. Thus, the vectorial product will give
	 * 		us the versor for traverse the canvas
	 */
	public ArrayList<Ray> generateRays(){
		ArrayList<Ray> rays = new ArrayList<Ray>();
		Vector3d parallelVersorToCanvas = new Vector3d();
		Vector3d diffPosition = new Vector3d();
		Point3d upperLeftPoint = new Point3d();
		
		//Calculate the step size from the formula stated above
		int width = canvas.getWidth();
		int height = canvas.getHeight();
		double angle = toRadians(visualAngle/2);
		double stepSize =  2 *  ( this.getCanvasDistance() / width ) * tan(angle);
		stepSize = abs(stepSize);		
		
		//Finds out the parallel versor to the canvas plane
		Vector3d cameraDirectionVersor = this.getCameraDirection();		
		Vector3d verticalReferenceVersor = getCameraVerticalReference();
		verticalReferenceVersor.normalize();
		parallelVersorToCanvas.cross(cameraDirectionVersor, verticalReferenceVersor);		
		
		//Calculate the upper left corner of the plate
		Vector3d diffHorizontal = new Vector3d(parallelVersorToCanvas);
		Vector3d diffVertical = new Vector3d(verticalReferenceVersor);
		
		diffVertical.scale( getScaleFactor(height, stepSize) );
		diffHorizontal.scale( getScaleFactor(width, stepSize) );
		diffPosition.sub( diffVertical, diffHorizontal );		
		upperLeftPoint.add(canvasPosition, diffPosition); 
				
		//Traverse the canvas and store the rays		
		Point3d nextRowPointer = new Point3d(upperLeftPoint);
		Vector3d deltaHorizontal = new Vector3d(parallelVersorToCanvas);
		Vector3d deltaVertical = new Vector3d(verticalReferenceVersor);		
		deltaVertical.scale(stepSize);
		deltaHorizontal.scale(stepSize);		
		for( int y = 0 ; y < height ; y++ ) {
			Point3d currentCanvasPoint = new Point3d(nextRowPointer);
			for ( int x = 0; x < width; x++) {
				System.out.println("(row=" + y + ", col=" + x +")Current CanvasPoint = " + currentCanvasPoint);//DEBUG
				rays.add(new Ray( new Point3d(eyePosition), new Point3d(currentCanvasPoint)));
				currentCanvasPoint.add(deltaHorizontal); 
			}
			nextRowPointer.sub(deltaVertical);
		}		
		return rays;		
	}	
	
	public String toString(){
		StringBuffer str = new StringBuffer();
		str.append("Eye Position : " + eyePosition + "\nCanvas Position : " +  canvasPosition + "\nVertical Reference Point = " + verticalReferencePoint + "\nVisual Angle (Sexagesimal) : " + ceil(visualAngle*180/PI) + "\nCanvas Info : " + canvas);
		return str.toString();
	}
	
	public Vector3d getCameraDirection(){
		Vector3d versor = new Vector3d();
		versor.sub(canvasPosition, eyePosition);
		versor.normalize();
		return versor;
	}
	
	public Vector3d getCameraVerticalReference(){
		Vector3d versor = new Vector3d();
		versor.sub(verticalReferencePoint, eyePosition);
		versor.normalize();
		return versor;
	}
	
	private double getScaleFactor( int dimension, double stepSize ){
		if( dimension % 2 == 0 )
			return stepSize/2 * (dimension-1);
		else
			return stepSize * (dimension/2);
	}
}
